/*******************************************************************************
 * Copyright (c) 2005 Koji Hisano <hisano@users.sourceforge.net>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *     Koji Hisano - initial API and implementation
 *******************************************************************************/
package jp.sf.mapswidgets;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;

/**
 * Instances of this class represent a type of map overlay that shows an icon at a single point on the map.
 * <p>
 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">Class Reference &gt; GMarker</a>].
 * </p>
 *
 * @see Polyline
 */
public final class Marker extends Overlay {
	private Point point;
	private Icon icon;

	private List<MarkerListener> listeners = new ArrayList<MarkerListener>();
	private StatusTextListener listener;

	/**
	 * Construct a new instance of this class given the point.
	 *
	 * @param point the point
	 */
	public Marker(Point point) {
		this(point, null);
	}

	/**
	 * Construct a new instance of this class given the point and the icon.
	 *
	 * @param point the point
	 * @param icon the icon
	 */
	public Marker(Point point, Icon icon) {
		setPointFieldOnly(point);
		this.icon = icon;
	}

	/**
	 * Get the point.
	 * <p>
	 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GPoint_code_">Class Reference &gt; GPoint</a>].
	 * </p>
	 * 
	 * @return the point
	 */
	public Point getPoint() {
		return point;
	}

	/**
	 * Set the point.
	 * <p>
	 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GPoint_code_">Class Reference &gt; GPoint</a>].
	 * </p>
	 * 
	 * @param point the point
	 */
	public void setPoint(Point point) {
		setPointFieldOnly(point);
		reset();
	}
	
	private void setPointFieldOnly(Point point) {
		CheckUtils.isNotNull("point", point);
		this.point = point;
	}


	/**
	 * Get the icon.
	 * <p>
	 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GIcon_code_">Class Reference &gt; GIcon</a>].
	 * </p>
	 * 
	 * @return the icon
	 */
	public Icon getIcon() {
		return icon;
	}

	/**
	 * Set the icon.
	 * <p>
	 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GIcon_code_">Class Reference &gt; GIcon</a>].
	 * </p>
	 * 
	 * @param icon the icon
	 */
	public void setIcon(Icon icon) {
		this.icon = icon;
		reset();
	}
	
	private void reset() {
		if (isAdded()) {
			GoogleMaps widget = getWidget();
			widget.removeOverlay(this);
			widget.addOverlay(this);
		}
	}

	/**
	 * Removes the marker from the map.
	 */
	public void dispose() {
		if (isAdded()) {
			getWidget().removeOverlay(this);
		}
	}

	String getId() {
		return "Marker" + super.getId();
	}

	void init(GoogleMaps widgets) {
		super.init(widgets);
		initListener();
	}

	void destroy(GoogleMaps widgets) {
		destroyListener(true);
		super.destroy(widgets);
	}
	
	String getStatementsToPrepare() {
		if (icon != null) {
			return icon.getExpressionToPrepare();
		} else {
			return super.getStatementsToPrepare();
		}
	}

	String getExpressionToCreate() {
		if (icon != null) {
			return "new GMarker(" + point.getExpression() + ", map.icon)";
		} else {
			return "new GMarker(" + point.getExpression() + ")";
		}
	}

	/**
	 * Displays the info window with the given HTML content.
	 * <p>
	 * See Google Maps API documentation <a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">[Class Reference &gt; GMarker &gt; openInfoWindowHtml(htmlStr)]</a>.
	 * </p>
	 * 
	 * @param html the content of the info window
	 */
	public void showInfoWindow(String html) {
		if (icon != null && icon.getOffsetOfInfoWindow() == null) {
			throw new SWTException("icon has no offset of info window (use Icon.setOffsetOfInfoWindow method to resolve)");
		}
		check();
		getWidget().execute(getVariable() + ".openInfoWindowHtml('" + html + "');");
	}

	/**
	 * Shows a blowup of the map.
	 * <p>
	 * See Google Maps API documentation <a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">[Class Reference &gt; GMarker &gt; showMapBlowup()]</a>.
	 * </p>
	 */
	public void showMapWindow() {
		check();
		getWidget().execute(getVariable() + ".showMapBlowup();");
	}
	
	/**
	 * Shows a blowup of the map.
	 * <p>
	 * See Google Maps API documentation <a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">[Class Reference &gt; GMarker &gt; showMapBlowup(zoomLevel)]</a>.
	 * </p>
	 * 
	 * @param zoomLevel the zoom level in the map window
	 */
	public void showMapWindow(int zoomLevel) {
		check();
		getWidget().execute(getVariable() + ".showMapBlowup(" + zoomLevel + ");");
	}
	
	/**
	 * Shows a blowup of the map.
	 * <p>
	 * See Google Maps API documentation <a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">[Class Reference &gt; GMarker &gt; showMapBlowup(zoomLevel, mapType)]</a>.
	 * </p>
	 * 
	 * @param zoomLevel the zoom level in the map window
	 * @param type the map type in the map window
	 */
	public void showMapWindow(int zoomLevel, MapType type) {
		check();
		getWidget().execute(getVariable() + ".showMapBlowup(" + zoomLevel + ", G_" + type + "_TYPE);");
	}

	private void check() {
		if (!isAdded()) {
			throw new SWTException("maker is not added to map");
		}
	}

	/**
	 * Adds the listener to the collection of listeners who will
	 * be notified when the Marker status is changed, by sending
	 * it one of the messages defined in the <code>MarkerListener</code>
	 * interface.
	 * <p>
	 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GMarker_code_">Class Reference &gt; GMarker &gt; Events</a>].
	 * </p>
	 * <p>
	 * <code>clicked()</code> is called when the user clicks the marker.
	 * <code>infoWindowOpend()</code> is called after the info window is displayed.
	 * <code>infoWindowClosed()</code> is called after the info window is closed.
	 * </p>
	 *
	 * @param listener the listener which should be notified
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 *
	 * @see MarkerListener
	 * @see MarkerAdapter
	 * @see #removeMarkerListener
	 */
	public void addMarkerListener(MarkerListener listener) {
		if (listener == null) {
			throw new SWTException(SWT.ERROR_NULL_ARGUMENT);
		}
		listeners.add(listener);
		initListener();
	}

	/**
	 * Removes the listener from the collection of listeners who will
	 * be notified when the Marker status is changed.
	 *
	 * @param listener the listener which should no longer be notified
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 *
	 * @see MarkerListener
	 * @see MarkerAdapter
	 * @see #addMarkerListener
	 */
	public void removeMarkerListener(MarkerListener listener) {
		if (listener == null) {
			throw new SWTException(SWT.ERROR_NULL_ARGUMENT);
		}
		listeners.remove(listener);
		destroyListener(false);
	}

	private void initListener() {
		if (!listeners.isEmpty() && listener == null && isAdded()) {
			listener = new StatusTextListener() {
				public void changed(StatusTextEvent event) {
					String header = getId() + ":";
					if (event.text.startsWith(header)) {
						getWidget().execute("window.status = '';");
						handleEvent(event.text.substring(header.length()));
					}
				}
			};
			getWidget().getBrowser().addStatusTextListener(listener);
			getWidget().execute(""
					+ getVariable() + ".click = function() {window.status = '" + getId() + ":click';}; GEvent.addListener(" + getVariable() + ", 'click', " + getVariable() + ".click);\n"
					+ getVariable() + ".infowindowopen = function() {window.status = '"	+ getId() + ":infowindowopen';}; GEvent.addListener(" + getVariable() + ", 'infowindowopen', " + getVariable() + ".infowindowopen);\n"
					+ getVariable() + ".infowindowclose = function() {window.status = '" + getId() + ":infowindowclose';}; GEvent.addListener(" + getVariable() + ", 'infowindowclose', " + getVariable() + ".infowindowclose);");
		}
	}

	void handleEvent(String event) {
		if ("click".equals(event)) {
			for (MarkerListener listener : listeners) {
				listener.clicked();
			}
		} else if ("infowindowopen".equals(event)) {
			for (MarkerListener listener : listeners) {
				listener.infoWindowOpend();
			}
		} else if ("infowindowclose".equals(event)) {
			for (MarkerListener listener : listeners) {
				listener.infoWindowClosed();
			}
		}
	}

	private void destroyListener(boolean force) {
		if ((force || listeners.isEmpty()) && listener != null) {
			getWidget().getBrowser().removeStatusTextListener(listener);
			getWidget().execute(""
					+ "GEvent.removeListener(" + getVariable() + ".click);\n"
					+ "GEvent.removeListener(" + getVariable() + ".infowindowopen);\n"
					+ "GEvent.removeListener(" + getVariable() + ".infowindowclose);");
			listener = null;
		}
	}
}
